module nade.angle;

import std.math;
import std.conv;

import derelict.opengl.gl;

import nade.vector;
import nade.random;


struct Angle(real R) {
	real value = 0;

	Angle dup() const { return Angle(value); }

	Angle opAssign(const Angle rhs) { value = rhs.value; return this; }
	Angle opNeg() const { return Angle(-value); }
	Angle opAdd(const Angle rhs) const { return Angle(value + rhs.value); }
	Angle opSub(const Angle rhs) const { return Angle(value - rhs.value); }
	Angle opMul(const Angle rhs) const { return Angle(value * rhs.value); }
	Angle opDiv(const Angle rhs) const { return Angle(value / rhs.value); }
	Angle opAdd(real rhs) const { return Angle(value + rhs); }
	Angle opSub(real rhs) const { return Angle(value - rhs); }
	Angle opMul(real rhs) const { return Angle(value * rhs); }
	Angle opDiv(real rhs) const { return Angle(value / rhs); }
	Angle opAddAssign(const Angle rhs) { value += rhs.value; return this; }
	Angle opSubAssign(const Angle rhs) { value -= rhs.value; return this; }
	Angle opMulAssign(const Angle rhs) { value *= rhs.value; return this; }
	Angle opDivAssign(const Angle rhs) { value /= rhs.value; return this; }
	Angle opAddAssign(real rhs) { value += rhs; return this; }
	Angle opSubAssign(real rhs) { value -= rhs; return this; }
	Angle opMulAssign(real rhs) { value *= rhs; return this; }
	Angle opDivAssign(real rhs) { value /= rhs; return this; }

	real opCast() const { return value; }
	string toString() const { return to!(string)(); }

	T to(T : string)() const { return .to!(string)(value); }
	T to(T)() const { return T(value*T.SCALE/SCALE); }

	Angle normal() const
	{
		auto r = dup;
		while(r.value <= -R*0.5) r.value += R;
		while(r.value > R*0.5) r.value -= R;
		return r;
	}

	real degrees() const
	{
		static if(R == 360)
			return value;
		else
			return value*360/R;
	}
	real radians() const
	{
		static if(R == 2*PI)
			return value;
		else
			return value*(2*PI)/R;
	}

	void rotate() const
	{
		glRotatef(degrees, 0, 0, 1);
	}
	void rotate(Vector3 axis) const
	{
		glRotatef(degrees, axis.x, axis.y, axis.z);
	}

	real sin() const { return .sin(radians); }
	real cos() const { return .cos(radians); }
	real tan() const { return .cos(radians); }

	static Angle acos(real rhs)
	{
		static if(R == 2*PI)
			return Angle(.acos(rhs));
		else
			return Angle(.acos(rhs)*R/(2*PI));
	}
	static Angle asin(real rhs)
	{
		static if(R == 2*PI)
			return Angle(.asin(rhs));
		else
			return Angle(.asin(rhs)*R/(2*PI));
	}
	static Angle atan(real rhs)
	{
		static if(R == 2*PI)
			return Angle(.atan(rhs));
		else
			return Angle(.atan(rhs)*R/(2*PI));
	}
	static Angle atan2(real x, real y)
	{
		static if(R == 2*PI)
			return Angle(.atan2(x, y));
		else
			return Angle(.atan2(x, y)*R/(2*PI));
	}
	static Angle atan2(Vector2 rhs)
	{
		static if(R == 2*PI)
			return Angle(.atan2(rhs.x, rhs.y));
		else
			return Angle(.atan2(rhs.x, rhs.y)*R/(2*PI));
	}

	static Angle random() { return Angle(.random(-R*0.5, R*0.5)); }

	static const SCALE = R;
	static const ZERO = Angle(0);
	static const FULL = Angle(R);
	static const HALF = Angle(R*0.5);
	static const Q0 = ZERO;
	static const Q1 = Angle(R*0.25);
	static const Q2 = HALF;
	static const Q3 = Angle(R*0.75);
	static const Q4 = ZERO;
}


alias Angle!(360) Degrees;
alias Angle!(PI*2) Radians;
